import { Spin } from 'antd';
import cn from 'classnames';
import React, { useEffect, useRef, useState } from 'react';
import styles from './SliderCaptcha.less';

import type { MouseEventHandler } from 'react';

interface SliderCaptchaProp {
  onSuccess?: () => void;
  onFail?: () => void;
}

export const SliderCaptcha: React.FC<SliderCaptchaProp> = (props) => {
  const { onSuccess, onFail } = props;

  const [loading, setLoading] = useState(true);
  const [isDragging, setIsDragging] = useState(false);
  const [targetX, setTargetX] = useState(0);
  const [sliderX, setSliderX] = useState(0);
  const [puzzleX, setPuzzleX] = useState(0);

  const [checkResult, setCheckResult] = useState('');
  const sliderRef = useRef<HTMLDivElement | null>(null);
  const trackRef = useRef<HTMLDivElement | null>(null);
  const abortControllerRef = useRef<AbortController | null>(null);

  const bgCanvasRef = useRef<HTMLCanvasElement | null>(null);
  const puzzleCanvasRef = useRef<HTMLCanvasElement | null>(null);

  const createPuzzlePath = (
    ctx: CanvasRenderingContext2D,
    x: number,
    y: number,
    size: number,
  ) => {
    ctx.beginPath();

    const radius = size / 6;
    const side = radius / Math.sqrt(2);
    const angle = Math.PI / 4;

    const centerX = x + size / 2;
    const centerY = y + size / 2;

    ctx.moveTo(x, y);

    ctx.lineTo(centerX - side, y);
    const x1 = centerX;
    const y1 = y - side;
    const startAngle1 = angle * 3;
    const endAngle1 = angle * 1;
    ctx.arc(x1, y1, radius, startAngle1, endAngle1, false);
    ctx.lineTo(x + size, y);

    ctx.lineTo(x + size, centerY - side);
    const x2 = x + size + side;
    const y2 = centerY;
    const startAngle2 = angle * 5;
    const endAngle2 = angle * 3;
    ctx.arc(x2, y2, radius, startAngle2, endAngle2, false);
    ctx.lineTo(x + size, y + size);

    ctx.lineTo(centerX + side, y + size);
    const x3 = centerX;
    const y3 = y + size - side;
    const startAngle3 = angle * 1;
    const endAngle3 = angle * 3;
    ctx.arc(x3, y3, radius, startAngle3, endAngle3, true);
    ctx.lineTo(x, y + size);

    ctx.lineTo(x, y);
    ctx.closePath();
  };

  const initCaptcha = async () => {
    setLoading(true);

    const bgCanvas = bgCanvasRef.current as HTMLCanvasElement;
    const bgCtx = bgCanvas.getContext('2d') as CanvasRenderingContext2D;
    bgCanvas.width = 300;
    bgCanvas.height = 150;

    const puzzleCanvas = puzzleCanvasRef.current as HTMLCanvasElement;
    const puzzleCtx = puzzleCanvas.getContext('2d') as CanvasRenderingContext2D;

    const radius = 50 / 6;
    const side = radius / Math.sqrt(2);

    const puzzleWidth = 50 + side + radius;
    const puzzleHeight = 150;
    puzzleCanvas.width = puzzleWidth;
    puzzleCanvas.height = puzzleHeight;

    const bgImg = new Image();
    bgImg.crossOrigin = 'Anonymous';
    bgImg.src = 'https://picsum.photos/300/150?random=' + Math.random();

    new Promise<HTMLImageElement>((resolve, reject) => {
      bgImg.onload = () => {
        setLoading(false);

        resolve(bgImg);
      };
      bgImg.onerror = reject;
    })
      .then((image) => {
        bgCtx.drawImage(image, 0, 0, bgCanvas.width, bgCanvas.height);

        const minX = bgCanvas.width * 0.2;
        const maxX = bgCanvas.width * 0.8 - 50;
        const randomX = Math.floor(Math.random() * (maxX - minX) + minX);
        const randomY = Math.floor(bgCanvas.height * 0.5 - 25);

        setTargetX(randomX);

        createPuzzlePath(bgCtx, randomX, randomY, 50);

        bgCtx.save();
        bgCtx.clip();

        bgCtx.fillStyle = 'rgba(255, 255, 255, 0.6)';
        bgCtx.fillRect(0, 0, 300, 150);

        bgCtx.strokeStyle = 'rgba(255, 255, 255, 1)';
        bgCtx.lineWidth = 1;
        bgCtx.stroke();
        bgCtx.restore();

        createPuzzlePath(puzzleCtx, 0, randomY, 50);
        puzzleCtx.clip();

        const copyX = (bgImg.width / bgCanvas.width) * randomX;
        const copyY = 0;
        const copyW = (bgImg.width / bgCanvas.width) * puzzleWidth;
        const copyH = bgImg.height;

        puzzleCtx.drawImage(
          bgImg,
          copyX,
          copyY,
          copyW,
          copyH,
          0,
          0,
          puzzleWidth,
          puzzleHeight,
        );

        puzzleCtx.shadowColor = 'rgba(0, 0, 0, 0.3)';
        puzzleCtx.shadowBlur = 8;
        puzzleCtx.shadowOffsetX = 2;
        puzzleCtx.shadowOffsetY = 2;
        puzzleCtx.fillStyle = 'rgba(0, 0, 0, 0.1)';
        puzzleCtx.fill();
      })
      .catch(() => {
        setTimeout(() => {
          initCaptcha();
        }, 1000);
      })
      .finally(() => {
        setLoading(false);
      });
  };

  const onVerify = (distance: number) => {
    const threshold = 5;
    const isSuccess = Math.abs(distance - targetX) <= threshold;

    if (isSuccess) {
      setCheckResult('success');
      onSuccess?.();
    } else {
      setCheckResult('fail');
      onFail?.();
      setTimeout(() => {
        setCheckResult('');
        setSliderX(0);
        setPuzzleX(0);

        initCaptcha();
      }, 500);
    }
  };

  const handleMouseMove = (e: MouseEvent, start: number) => {
    const trackRect = trackRef.current?.getBoundingClientRect?.() as DOMRect;

    const move = e.clientX - trackRect.left - start;

    const sliderMove = Math.max(
      0,
      Math.min(
        move,
        trackRect.width - (sliderRef.current as HTMLDivElement).offsetWidth,
      ),
    );

    const puzzleMove = Math.max(
      0,
      Math.min(
        move,
        (bgCanvasRef.current as HTMLCanvasElement).width -
          (puzzleCanvasRef.current as HTMLCanvasElement).offsetWidth,
      ),
    );

    setPuzzleX(puzzleMove);
    setSliderX(sliderMove);
  };

  const handleMouseUp = (e: MouseEvent) => {
    setIsDragging(false);

    const trackRect = trackRef.current?.getBoundingClientRect?.() as DOMRect;
    const sliderRect = sliderRef.current?.getBoundingClientRect?.() as DOMRect;

    const distance = e.clientX - trackRect.left - (e.clientX - sliderRect.left);

    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
      abortControllerRef.current = null;
      console.log('事件监听已取消');
    }

    onVerify(distance);
  };

  useEffect(() => {
    initCaptcha();

    return () => {
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
        abortControllerRef.current = null;
        console.log('事件监听已取消');
      }
    };
  }, []);

  const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
    if (checkResult === 'success' || e.button !== 0 || loading) return;

    const sliderRect = sliderRef.current?.getBoundingClientRect?.() as DOMRect;
    const start = e.clientX - sliderRect.left;

    setIsDragging(true);

    const controller = new AbortController();
    abortControllerRef.current = controller;
    const { signal } = controller;

    document.addEventListener(
      'mousemove',
      (event: MouseEvent) => handleMouseMove(event, start),
      { signal },
    );
    document.addEventListener('mouseup', handleMouseUp, { signal });
  };

  return (
    <div className={styles.container}>
      {loading && (
        <div className={styles.loading}>
          <Spin />
        </div>
      )}
      <canvas ref={bgCanvasRef} id="bgCanvas" className={styles.backgroud} />
      <canvas
        ref={puzzleCanvasRef}
        id="puzzle"
        className={styles.puzzle}
        style={{
          left: `${puzzleX}px`,
        }}
      />
      <div className={styles.track} ref={trackRef}>
        <div
          className={cn(styles.indicator, {
            [styles.visible]: sliderX,
            [styles.dragging]: isDragging,
            [styles.verifySuccess]: checkResult === 'success',
            [styles.verifyFail]: checkResult === 'fail',
          })}
          style={{
            width: `${sliderX + 36}px`,
          }}
        />
        <div
          className={cn(styles.slider, {
            [styles.disable]: loading,
            [styles.processing]: isDragging,
            [styles.success]: checkResult === 'success',
            [styles.fail]: checkResult === 'fail',
          })}
          style={{
            left: `${sliderX}px`,
          }}
          ref={sliderRef}
          onMouseDown={handleMouseDown}
        >
          <span>→</span>
        </div>
        {!isDragging && !checkResult && (
          <span className={styles.tip}>拖动滑块补全缺口图案</span>
        )}
      </div>
    </div>
  );
};

export default SliderCaptcha;
